#ifndef __FIBER_H__
#define __FIBER_H__

#include <iostream>
#include <functional>
#include <memory>
#include <ucontext.h>

// 协程的本质就是函数和函数运行状态的组合

// 上下文结构体定义
// typedef struct ucontext_t {
//     // 当前上下文结束后，下一个激活的上下文对象的指针，只在当前上下文是由makecontext创建时有效
//     struct ucontext_t *uc_link;
//     // 当前上下文的信号屏蔽掩码
//     sigset_t          uc_sigmask;
//     // 当前上下文使用的栈内存空间，只在当前上下文是由makecontext创建时有效
//     stack_t           uc_stack;
//     // 平台相关的上下文具体内容，包含寄存器的值
//     mcontext_t        uc_mcontext;
// } ucontext_t;

class Fiber : public std::enable_shared_from_this<Fiber>
{
public:
    typedef std::shared_ptr<Fiber> ptr;

    // 定义协程的状态
    typedef enum Statu
    {
        READY,   // 就绪
        RUNNING, // 运行
        TERM     // 结束
    } Statu;

private:
    /**
     * @brief 构造函数
     * @attention 无参构造函数只能用于创建线程的第一个协程,也就是主函数对应的协程
     * 这个协程只能由GetThis方法调用,所以定义为私有方法
     */
    Fiber();

public:
    /**
     * @brief 构造函数
     * @param[in] cb 协程入口函数
     * @param[in] stacksize 栈大小
     * @param[in] run_in_scheduler 本协程是否参与调度器调度,默认为true
     */
    Fiber(const std::function<void()>& cb, size_t stacksize = 128, bool run_in_scheduler = true);
    ~Fiber();

    // 重置协程入口函数,复用栈空间,不重新创建栈
    void reset(std::function<void()> cb);

    /**
     * @brief 将当前协程切换到运行状态
     * @details 当前协程和正在运行的协程进行交换，前者状态变为RUNNING，后者状态变为READY
     */
    void resume();

    /**
     * @brief 当前协程让出执行权
     * @details 当前协程与上次resume时退到后台的协程进行交换，前者状态变为READY,后者状态变为RUNNING
     */
    void yield();

    // 获取协程ID
    uint64_t getId() const { return m_id; }
    // 获取协程状态
    Statu getState() const { return m_statu; }

    // 设置当前正在运行的协程，设置线程局部变量t_fiber的值
    static void SetThis(Fiber *fiber);

    /**
     * @brief 返回线程正在运行的协程
     * @details 如果当前线程还未创建协程,则创建线程的第一个协程
     * 且该协程为当前线程的主协程,其他协程都通过这个协程来调度,也就是说
     * 其他协程结束时,都要切回到主协程,由主协程重新选择新的协程进行resume
     * @attention 线程如果要创建协程,那么应该首先执行一下Fiber::GetThis()操作,以初始化主协程
     */
    static Fiber::ptr GetThis();

    // 获取总协程数
    static uint64_t TotalFibers();
    // 协程入口函数
    static void MainFunc();
    // 获取当前协程ID
    static uint64_t GetFiberId();

private:
    // 协程ID
    uint64_t m_id = 0;
    // 协程栈大小
    uint32_t m_stacksize = 0;
    // 协程状态
    Statu m_statu = READY;
    // 协程上下文
    ucontext_t m_ctx;
    // 协程栈地址
    void *m_stack = nullptr;
    // 协程入口函数
    std::function<void()> m_cb;
    // 协程是否参与调度器调度
    bool m_runInScheduler;
};

#endif